package com.yejiarong.elasticsearch.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.OpType;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.yejiarong.common.validate.ValidateUtils;
import com.yejiarong.elasticsearch.api.bean.request.DeleteByQueryForm;
import com.yejiarong.elasticsearch.api.bean.request.GetRequestForm;
import com.yejiarong.elasticsearch.api.bean.request.SaveRequestForm;
import com.yejiarong.elasticsearch.api.bean.request.SearchRequestForm;
import com.yejiarong.elasticsearch.api.bean.response.DeleteByQueryResult;
import com.yejiarong.elasticsearch.api.bean.response.DeleteResult;
import com.yejiarong.elasticsearch.api.bean.response.SaveResult;
import com.yejiarong.elasticsearch.api.bean.response.SearchPageResult;
import com.yejiarong.elasticsearch.api.enums.QueryType;
import com.yejiarong.elasticsearch.api.enums.Result;
import com.yejiarong.elasticsearch.common.tools.TimeUnitUtils;
import com.yejiarong.elasticsearch.service.IElasticsearchService;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author YeJR
 */
@Service
public class ElasticsearchServiceImpl implements IElasticsearchService {

    private final ElasticsearchClient elasticsearchClient;

    public ElasticsearchServiceImpl(ElasticsearchClient elasticsearchClient) {
        this.elasticsearchClient = elasticsearchClient;
    }


    /**
     * 文档是否存在
     *
     * @param index 索引
     * @param documentId 文档ID
     * @return 是否存在
     */
    @SneakyThrows
    @Override
    public boolean documentExists(String index, String documentId) {
        BooleanResponse booleanResponse = elasticsearchClient.exists(i -> i.index(index).id(documentId));
        return booleanResponse != null && booleanResponse.value();
    }

    /**
     * 索引文档
     * @param saveRequestForm 请求
     */
    @SneakyThrows
    @Override
    public SaveResult indexDocument(SaveRequestForm saveRequestForm) {
        ValidateUtils.valid(saveRequestForm);

        IndexRequest<Object> indexRequest = IndexRequest.of(b->
                b.index(saveRequestForm.getIndex()).id(saveRequestForm.getDocumentId()).document(saveRequestForm.getDocument()).opType(OpType.Create));
        IndexResponse indexResponse = elasticsearchClient.index(indexRequest);
        return new SaveResult(indexResponse.id(), Result.fromValue(indexResponse.result().jsonValue()));
    }

    /**
     * 更新文档
     *
     * @param saveRequestForm 请求
     * @return SaveResponse
     */
    @SneakyThrows
    @Override
    public SaveResult updateDocument(SaveRequestForm saveRequestForm) {
        ValidateUtils.valid(saveRequestForm);
        UpdateRequest<Object, Object> updateRequest = UpdateRequest.of(b ->
                b.index(saveRequestForm.getIndex()).id(saveRequestForm.getDocumentId()).doc(saveRequestForm.getDocument()));
        UpdateResponse<Object> updateResponse = elasticsearchClient.update(updateRequest, Object.class);
        return new SaveResult(updateResponse.id(), Result.fromValue(updateResponse.result().jsonValue()));
    }

    /**
     * 删除文档
     *
     * @param index      索引
     * @param documentId 文档ID
     * @return DeleteResponse
     */
    @SneakyThrows
    @Override
    public DeleteResult deleteDocument(String index, String documentId) {
        ValidateUtils.notBlank(index, "索引不能为空");
        ValidateUtils.notBlank(documentId, "文档ID不能为空");
        DeleteRequest deleteRequest = DeleteRequest.of(b -> b.index(index).id(documentId));
        DeleteResponse deleteResponse = elasticsearchClient.delete(deleteRequest);
        return new DeleteResult(deleteResponse.id(), Result.fromValue(deleteResponse.result().jsonValue()));
    }

    /**
     * 通过查询条件删除文档
     *
     * @param deleteByQueryForm 查询条件
     */
    @SneakyThrows
    @Override
    public DeleteByQueryResult deleteDocumentByQuery(DeleteByQueryForm deleteByQueryForm) {
        ValidateUtils.valid(deleteByQueryForm);

        Query query = buildQuery(deleteByQueryForm.getQueries());

        DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest.Builder()
                .index(deleteByQueryForm.getIndex())
                .query(query)
                .build();
        DeleteByQueryResponse deleteByQueryResponse = elasticsearchClient.deleteByQuery(deleteByQueryRequest);

        DeleteByQueryResult deleteByQueryResult = new DeleteByQueryResult();
        if (Objects.nonNull(deleteByQueryResponse)) {
            deleteByQueryResult.setDeleted(deleteByQueryResponse.deleted());
            deleteByQueryResult.setTotal(deleteByQueryResponse.total());
        }
        return deleteByQueryResult;
    }

    /**
     * 文档ID查询
     * @param getRequestForm 请求
     */
    @SneakyThrows
    @Override
    public Map<String, Object> getDocumentById(GetRequestForm getRequestForm) {
        ValidateUtils.valid(getRequestForm);
        GetRequest getRequest = GetRequest.of(b -> b.index(getRequestForm.getIndex()).id(getRequestForm.getDocumentId()));
        GetResponse<Object> response = elasticsearchClient.get(getRequest, Object.class);

        ValidateUtils.isTrue(response.found(), String.format("索引【%s】不存在此文档【%s】", getRequestForm.getIndex(), getRequestForm.getDocumentId()));
        Map<String, Object> documentMap = new HashMap<>();
        documentMap.put("id", response.id());
        documentMap.put("index", response.index());
        documentMap.put("version", response.version());
        documentMap.put("seqNo", response.seqNo());
        documentMap.put("source", response.source());
        return documentMap;
    }

    /**
     * 搜索文档
     * @param searchRequestForm 请求
     */
    @SneakyThrows
    @Override
    public SearchPageResult searchDocument(SearchRequestForm searchRequestForm) {
        ValidateUtils.valid(searchRequestForm);

        Query query = buildQuery(searchRequestForm.getQueries());

        SearchRequest.Builder builder = new SearchRequest.Builder()
                .index(searchRequestForm.getIndex())
                .query(query)
                .from(searchRequestForm.getPagination().getFrom())
                .size(searchRequestForm.getPagination().getPageSize());

        if (Objects.nonNull(searchRequestForm.getKeepalive()) && Objects.nonNull(searchRequestForm.getTimeUnit())) {
            String abbreviation = TimeUnitUtils.getAbbreviation(searchRequestForm.getTimeUnit());
            ValidateUtils.notBlank(abbreviation, "不支持的时间单位");
            builder.scroll(t -> t.time(searchRequestForm.getKeepalive() + abbreviation));
        }
        SearchRequest searchRequest = builder.build();
        SearchResponse<Object> searchResponse = elasticsearchClient.search(searchRequest, Object.class);

        // 解析结果
        SearchPageResult searchPageResult = new SearchPageResult();
        if (Objects.nonNull(searchResponse) && Objects.nonNull(searchResponse.hits())) {
            HitsMetadata<Object> hits = searchResponse.hits();
            long total = Optional.ofNullable(hits.total()).map(TotalHits::value).orElse(0L);
            searchPageResult.setTotal(total);
            searchPageResult.setPages(total, searchRequestForm.getPagination().getPageSize());
            searchPageResult.setScrollId(searchResponse.scrollId());

            List<Map<String, Object>> list = new ArrayList<>();
            List<Hit<Object>> hitList = Optional.ofNullable(hits.hits()).orElseGet(ArrayList::new);
            for (Hit<Object> hit : hitList) {
                Map<String, Object> data = JSON.parseObject(JSON.toJSONString(hit.source()), new TypeReference<>() {
                });
                data.put("id", hit.id());
                data.put("index", hit.index());
                list.add(data);
            }
            searchPageResult.setList(list);
        }
        return searchPageResult;
    }

    /**
     * 构建请求体
     * @param queries 查询条件
     * @return 查询条件
     */
    private Query buildQuery(List<com.yejiarong.elasticsearch.api.bean.request.query.Query> queries) {

        List<Query> queryList = queries.stream().map(q -> {
            if (QueryType.TERM.equals(q.getType())) {
                if (q.getValues().size() == 1) {
                    return TermQuery.of(t -> t.field(q.getField()).value(FieldValue.of(q.getValues().getFirst())))._toQuery();
                } else {
                    return TermsQuery.of(t -> t.field(q.getField()).terms(t2 -> t2
                            .value(q.getValues().stream().map(FieldValue::of).collect(Collectors.toList()))))._toQuery();
                }
            } else if (QueryType.MATCH.equals(q.getType())) {
                if (q.getValues().size() == 1) {
                    return MatchQuery.of(m -> m.field(q.getField()).query(FieldValue.of(q.getValues().getFirst())))._toQuery();
                }
            } else if (QueryType.MATCH_PHRASE.equals(q.getType())) {
                if (q.getValues().size() == 1) {
                    return MatchPhraseQuery.of(m -> m.field(q.getField()).query(String.valueOf(q.getValues().getFirst())))._toQuery();
                }
            } else if (QueryType.NUMBER_RANGE.equals(q.getType())) {
                return RangeQuery.of(r -> r.number(n -> {
                    NumberRangeQuery.Builder nRangeBuild = n.field(q.getField());
                    if (q.getValues().getFirst() != null) {
                        nRangeBuild.gte(Double.parseDouble(String.valueOf(q.getValues().getFirst())));
                    } else if (q.getValues().get(1) != null) {
                        nRangeBuild.lt(Double.parseDouble(String.valueOf(q.getValues().get(1))));
                    }
                    return nRangeBuild;
                }))._toQuery();
            }
            return null;
        }).filter(Objects::nonNull).toList();
        ValidateUtils.notEmpty(queries, "构建查询请求失败");

        return BoolQuery.of(q -> q.must(queryList))._toQuery();
    }

}
